

# 
#  Implementation of supported services backed by a JSON DB
#[todo]

from src.social.spi import *;
from src.gadgets.servlet import *;
from src.social.service import ResponseItem;
from google.appengine.ext import db


class SaveDb(db.Model):
	dbStr = db.TextProperty();

class JsonDbOpensocialService:# implements ActivitiesService, PeopleService, AppDataService, MessagesService {
	# 
	#  db["activities"] . Array<Person>
	
	__PEOPLE_TABLE = "people";
	
	# 
	#  db["people"] . Map<Person.Id, Array<Activity>>
	
	__ACTIVITIES_TABLE = "activities";
	
	# 
	#  db["data"] . Map<Person.Id, Map<String, String>>
	
	__DATA_TABLE = "data";
	
	# 
	#  db["friendLinks"] . Map<Person.Id, Array<Person.Id>>
	
	__FRIEND_LINK_TABLE = "friendLinks";
	
	# 
	#  db["userApplications"] . Map<Person.Id, Array<Application Ids>>
	
	__USER_APPLICATIONS_TABLE = "userApplications";

	
	def __init__(self):
		# 
		#  The DB
		
		self.__db = None;

		self.__allPeople_ = {};
		self.__allData_ = {};
		self.__allActivities_ = {};
		self.__jsonDbFileName_ = 'ShindigDb.json';

	

	def getDb(self):
		try:
			import os;
			fileName = os.path.realpath(os.path.dirname(__file__)+"/"+self.__jsonDbFileName_);
			dbStr = "";
			dbData = db.GqlQuery("SELECT * FROM SaveDb");			
			if (dbData.count()>0):	
				for data in dbData:
#					self.response.out.write("<br>");
#					self.response.out.write(data.dbStr);					
					dbStr = data.dbStr;
#					self.response.out.write(greeting.dbStr);
				from lib import Util;
				#dbStr = dbStr.encode("utf8");				
				jsonDecoded = Util.json_decode(dbStr);
				if (jsonDecoded == dbStr):
					raise Exception, "Failed to decode the json db";
			else:
				from lib import Config;
				jsonDb = Config.get('jsondb_path');
				if (not os.path.exists(fileName)):
					raise Exception, "Could not read json db file: jsonDb, check if the file exists & has proper permissions";
				
				try:
					fp = open(fileName, 'r');
					dbConfig = fp.read();
					fp.close();
				except:
					raise Exception, "Could not read json db file: jsonDb, check if the file exists & has proper permissions";
				
				from lib import Util;
				import re;
				contents = dbConfig;
				'''pattern = re.compile('@/\\*(?:.|[\\n\\r])*?\\*/@');
				contents = pattern.sub('', dbConfig);
				pattern = re.compile('@/[^http:\/\/|^https:\/\/]\/\/.*$/@');
#				pattern = re.compile('/\/\/(.*)/');
				contents = pattern.sub('', contents);'''

				#contents = preg_replace('/[^http:\/\/|^https:\/\/]\/\/.*/m', '', preg_replace('@/\\*(?:.|[\\n\\r])*?\\*/@', '', dbConfig));				

				jsonDecoded = Util.json_decode(contents, True);				
				if (jsonDecoded == contents):
					raise Exception, "Failed to decode the json db";
				
				self.__saveDb_(contents);
			return jsonDecoded;
			
		except Exception, e: 
			raise Exception, e;
		
	def __saveDb_(self, dbStr):
		from lib import Util;
		dbData = db.GqlQuery("SELECT * FROM SaveDb");		
		if (dbData.count()>0) :
			for data in dbData:
				dbVal = data;
			dbVal.dbStr= db.Text(Util.json_encode(dbStr));
			#dbVal.dbStr= db.Text((Util.json_encode(dbStr)).encode("utf8"));

		else : 
			dbVal = SaveDb();
			dbVal.dbStr=  db.Text(dbStr.decode("utf8"));
		dbVal.put();

		
	# 
	#  Get the set of user id's from a user and group
	
	def __getIdSet_(self, user, group, token):
		userId = user.getUserId(token);

		if (group == None):
			return [userId];
		
		returnVal = {};
		if (group.getType() == 'self'):
			returnVal[userId] = userId;
		else:
			#case 'all':
			#case 'friends':
			#case 'groupId':
			db = self.getDb();
			friendsLinkTable = db[JsonDbOpensocialService.__FRIEND_LINK_TABLE];
			if (friendsLinkTable.has_key(userId)):
				friends = friendsLinkTable[userId];
				for friend in friends:
					returnVal[friend['id']] = friend;

		return returnVal;

	def __getAllPeople_(self):
		db = self.getDb();
		peopleTable = db[JsonDbOpensocialService.__PEOPLE_TABLE];
		for people in peopleTable:
			self.__allPeople_[people['id']] = people;
		
		db[JsonDbOpensocialService.__PEOPLE_TABLE] = self.__allPeople_;
		return self.__allPeople_;

	def __getAllData_(self):		
		db = self.getDb();
		dataTable = db[JsonDbOpensocialService.__DATA_TABLE];		
		for key in dataTable.keys():
			self.__allData_[key] = dataTable[key];

		db[JsonDbOpensocialService.__DATA_TABLE] = self.__allData_;
		
		return self.__allData_;

	def __getAllActivities_(self):
		db = self.getDb();
		activitiesTable = db[JsonDbOpensocialService.__ACTIVITIES_TABLE];
		for key in activitiesTable.keys():
			self.__allActivities_[key] = activitiesTable[key];
		
		db[JsonDbOpensocialService.__ACTIVITIES_TABLE] = self.__allActivities_;
		return self.__allActivities_;
	

	def __getPeopleWithApp_(self, appId):
		peopleWithApp = [];
		db = self.getDb();
		userApplicationsTable = db[JsonDbOpensocialService.USER_APPLICATIONS_TABLE];
		
		for key in userApplicationsTable.keys():
			if (userApplicationsTable[key].has_key(appId)):
				peopleWithApp.append(key);

		return peopleWithApp;


	def getPerson(self, userId, groupId, fields, token):
		person = self.getPeople(userId, groupId, CollectionOptions(), fields, token);
		# return of getPeople is a ResponseItem(RestfulCollection(ArrayOfPeople)), disassemble to return just one person
		person = person.getResponse().getEntry();
		from lib import Util;
		if (Util.is_array(person) and len(person) == 1):
			return ResponseItem(None, None, person[0]);
		
		return ResponseItem(NOT_FOUND, "Person not found", None);


	
	def getPeople(self, userId, groupId, options, fields, token):
		sortOrder = options.getSortOrder();
		filter = options.getFilterBy();
		first = options.getStartIndex();
		max = options.getCount();
		networkDistance = options.getNetworkDistance();
		
		db = self.getDb();
		friendsTable = db[JsonDbOpensocialService.__FRIEND_LINK_TABLE];
		ids = [];
		group = groupId.getType();
		
		from lib import Util;

		if(group == 'all' or group == 'friends'):
			#case 'all':
			#case 'friends':
			
			if (Util.is_array(friendsTable) and len(friendsTable) > 0 and friendsTable.has_key(userId.getUserId(token))):
				ids = friendsTable[userId.getUserId(token)];
			
		else:
			#case 'self':
			#default:
			ids.append(userId.getUserId(token));

		allPeople = self.__getAllPeople_();
		if (not token.isAnonymous() and filter == "hasApp"):
			appId = token.getAppId();
			peopleWithApp = self.getPeopleWithApp(appId);
		
		people = [];
		for id in ids:
			if (filter == "hasApp" and not id in peopleWithApp):
				continue;
			
			person = None;
			if (Util.is_array(allPeople) and allPeople.has_key(id)):
				person = allPeople[id];
				if (not token.isAnonymous() and id == token.getViewerId()):
					person['isViewer'] = True;
				
				if (not token.isAnonymous() and id == token.getOwnerId()):
					person['isOwner'] = True;
				
				if (not fields.has_key('@all')):
					newPerson = {};
					newPerson['isOwner'] = person['isOwner'] if person.has_key('isOwner') else False;
					newPerson['isViewer'] = person['isViewer'] if person.has_key('isViewer') else False;
					newPerson['name'] = person['name'];
					for field in fields.keys():
						present = fields[field];
						if (person.has_key(field) and not newPerson.has_key(field)):
							newPerson[field] = person[field];
						
					person = newPerson;
				
				people.append(person);

		if (sortOrder == 'name'):
			#usort(self, people, array(this, 'comparator'));
			#[todo]
			pass;
		
		# TODO: The samplecontainer doesn't support any filters yet. We should fix this.
		totalSize = len(people);
		last = first + max;
		last = min(last, totalSize);
		if (first != False and first != None and last):
			people = people[first : last];
		
		collection = RestfulCollection(people, first, totalSize);
		return ResponseItem(None, None, collection);
	



	def getPersonData(self, userId, groupId, fields, appId, token):
		db = self.getDb();
		allData = self.__getAllData_();
		friendsTable = db[JsonDbOpensocialService.__FRIEND_LINK_TABLE];
		data = {};
		ids = [];

		if(groupId.getType() == 'self'):
			ids.append(userId.getUserId(token));

		
		
		elif(groupId.getType() == 'all' or groupId.getType() == 'friends'):
			from lib import Util;
			if (Util.is_array(friendsTable) and len(friendsTable) > 0 and friendsTable.has_key(userId.getUserId(token))):
				ids = friendsTable[userId.getUserId(token)];
			
		else:
			return ResponseItem(NOT_IMPLEMENTED, "We don't support fetching data in batches yet", None);
		
		for id in ids:
			if (allData.has_key(id)):
				allPersonData = allData[id];
				personData = {};
				for key in allPersonData.keys():
					if (fields.has_key(key) or fields.has_key('@all')):
						personData[key] = allPersonData[key];
					

				data[id] = personData;

		return ResponseItem(None, None, RestfulCollection.createFromEntry(data));
	


	def updatePersonData(self, userId, groupId, fields, values, appId, token):		
		db = self.getDb();		
		for key in fields.keys():
			present = fields[key];
			if (not self.isValidKey(key)):
				return ResponseItem(BAD_REQUEST, "The person app data key had invalid characters", None);
			
		
		allData = self.__getAllData_();
		person = allData[userId.getUserId(token)];
		
		if(groupId.getType() == 'self'):
			for key in fields.keys():
				present = fields[key];
				value = values[key] if(values.has_key(key)) else None;
				person[key] =self.__decodeToUtf8_(value);
			
		else:
			return ResponseItem(NOT_IMPLEMENTED, "We don't support updating data in batches yet", None);

		allData[userId.getUserId(token)] = person;
		db[JsonDbOpensocialService.__DATA_TABLE] = allData;
		self.__saveDb_(db);
		return ResponseItem(None, None, {});
	
	
	def deletePersonData(self, userId, groupId, fields, appId, token):
		for key in fields.keys():
			present = fields[key];
			if (not self.isValidKey(key)):
				return ResponseItem(BAD_REQUEST, "The person app data key had invalid characters", None);
			
		if (groupId.getType() == 'self'):
		
			for key in fields.keys():
				# TODO: Implement this!  
				present = fields[key];
			
		else:
			return ResponseItem(NOT_IMPLEMENTED, "We don't support deleting data in batches yet", None);

		return ResponseItem(None, None, {});



	def getActivities(self, userId, groupId, first, max, token):
		db = self.getDb();
		friendsTable = db[JsonDbOpensocialService.__FRIEND_LINK_TABLE];
		ids = [];

		from lib import Util;
		if (groupId.getType() == 'self'):
			ids.append(userId.getUserId(token));
			
		else:
			if (Util.is_array(friendsTable) and len(friendsTable) > 0 and friendsTable.has_key(userId.getUserId(token))):
				ids = friendsTable[userId.getUserId(token)];
			
		allActivities = self.__getAllActivities_();
		activities = [];
		for id in ids:
			if (allActivities.has_key(id)):
				activities = Util.array_merge(activities, allActivities[id]);
			
		# TODO: Sort them
		return ResponseItem(None, None, RestfulCollection.createFromEntry(activities));
	
	

	def createActivity(self, userId, activity, token):
		db = self.getDb();
		activitiesTable = self.__getAllActivities_();
		activity['appId'] = token.getAppId();
		activity = self.__decodeToUtf8_(activity);
		activitiesTable[userId.getUserId(token)].append(activity);
		db[JsonDbOpensocialService.__ACTIVITIES_TABLE] = activitiesTable;
		self.__saveDb_(db);
		return ResponseItem(None, None, {});
	
	

	def getActivity(self, userId, groupId, activityId, first, max, token):
		activities = self.getActivities(userId, groupId, None, None, token);
		activities = activities.getResponse();
		if (isinstance(activities, RestfulCollection)):
			activities = activities.getEntry();
			for activity in activities:
				if (activity['id'] == activityId):
					return ResponseItem(None, None, activity);
				
		return ResponseItem(NOT_FOUND, "Activity not found", None);
	

	def createMessage(self, userId, message, token):
		return ResponseItem(NOT_IMPLEMENTED, "Not implemented", None);
	
	# 
	#  Determines whether the input is a valid key.
	# 
	#  @param key the key to validate.
	#  @return True if the key is a valid appdata key, False otherwise.
	@staticmethod
	def isValidKey(key):
		if (len(key) == 0):
			return False;
		for i in range(0, len(key)):
			c = key[i:i+1];
			if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or (c == '-') or (c == '_') or (c == '.')):
				continue;
			return False;
		
		return True;
	

	def __comparator_(self, person, person1):
		name = person['name']['unstructured'];
		name1 = person1['name']['unstructured'];
		if (name == name1):
			return 0;
		
		return -1 if (name < name1) else 1;

	def __decodeToUtf8_(self, obj) :
		import types;
		if type(obj) is types.StringType : 
			return obj.decode("utf8");
		elif type(obj) is types.DictType :
			for key in obj : 
				 obj[key] = self.__decodeToUtf8_(obj[key]);
		elif type(obj) is types.ListType :
			for key in obj : 
				 key = self.__decodeToUtf8_(key);
		return obj;